// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include "cangjie/AST/Match.h"

#include "NodeCast.h"
#include "Actions/Replace.h"
#include "Actions/Comment.h"
#include "Rules/Rule.h"

#include "Rules/CallExprMatchRule.h"

using namespace Cangjie::AST;

namespace CangjieMigrator::Rules {

// packageName, identifier, funcName, callerRange
using CallExprInfo = std::tuple<std::string, std::string, std::string, PositionRange>;

static std::optional<CallExprInfo> GetCallExprInfoRefExpr(const RefExpr &expr)
{
    const auto &ref = expr.ref;
    const auto target = ref.target;
    if (!target) {
        return std::nullopt;
    }
    return {{ target->fullPackageName, ref.identifier.Val(), target->identifier.Val(), PositionRange(expr) }};
}

static std::optional<CallExprInfo> GetCallExprInfoMemberAccess(const MemberAccess &memberAccess)
{
    if (memberAccess.target == nullptr) {
        return std::nullopt;
    }
    const auto target = memberAccess.target;
    if (!target) {
        return std::nullopt;
    }
    const auto baseExpr = memberAccess.baseExpr.get();
    if (!baseExpr) {
        return std::nullopt;
    }
    const auto ty = baseExpr->ty;
    if (!ty) {
        return std::nullopt;
    }
    return {{ target->fullPackageName, ty->name, memberAccess.field, PositionRange(*baseExpr) }};
}

// packageName, identifier, funcName, callerRange
static std::optional<CallExprInfo> GetBaseFuncInfo(const Expr &expr)
{
    if (expr.astKind == ASTKind::REF_EXPR) {
        return GetCallExprInfoRefExpr(Cast<ASTKind::REF_EXPR>(expr));
    }
    if (expr.astKind == ASTKind::MEMBER_ACCESS) {
        return GetCallExprInfoMemberAccess(Cast<ASTKind::MEMBER_ACCESS>(expr));
    }
    return std::nullopt;
}

CallExprMatchRule::CallExprMatchRule(std::string elementName, std::string funcType, std::string funcName)
    : elementName(std::move(elementName)), funcType(std::move(funcType)), funcName(std::move(funcName))
{}

MatchRule::Type CallExprMatchRule::GetType() const
{
    return MatchRule::Type::CallExpr;
}

static std::string RemoveSpace(const std::string &input)
{
    std::string result;
    for (const char c : input) {
        if (!::isspace(c)) {
            result += c;
        }
    }
    return result;
}

static std::string RemoveType(const std::string &input)
{
    std::string result;
    result.reserve(input.size());

    size_t begin = 0;
    const size_t end = input.size();
    for (size_t i = 0; i < end; i++) {
        if (input[i] == '-') {
            if (i == begin) {
                result.append(input.begin() + begin, input.begin() + (i + 1));
            }
            begin = i + 1;
        } else if (!std::isalpha(input[i])) {
            result.append(input.begin() + begin, input.begin() + (i + 1));
            begin = i + 1;
        }
    }
    if (begin != end) {
        result.append(input.begin() + begin, input.begin() + end);
    }
    return result;
}

static std::string RemoveSpaceAndType(const std::string &input)
{
    auto result1 = RemoveSpace(input);
    auto result2 = RemoveType(result1);
    return result2;
}

static bool MatchTypes(const std::string &ruleType, const std::string &extractType)
{
    const auto t1 = RemoveSpaceAndType(ruleType);
    const auto t2 = RemoveSpaceAndType(extractType);
    return t1 == t2;
}

bool CallExprMatchRule::MatchConditions(const ExtractedInfo *info)
{
    const auto callExprInfo = dynamic_cast<const CallExprExtractedInfo *>(info);
    if (!callExprInfo) {
        return false;
    }

    // 比较函数名与包名
    if (funcName != callExprInfo->funcName || elementName != callExprInfo->packageAndIdentifier) {
        return false;
    }

    // 比较函数类型
    if (!MatchTypes(funcType, callExprInfo->funcType)) {
        return false;
    }

    return true;
}

std::unique_ptr<MatchRule> CallExprMatchRule::TryBuild(const TomlConfig &config)
{
    if (config.title != "CallExpr") {
        return nullptr;
    }
    GET_VALUE_IF_EMPTY_RETURN_NULL(config, elementName);
    GET_VALUE_IF_EMPTY_RETURN_NULL(config, funcType);
    GET_VALUE_IF_EMPTY_RETURN_NULL(config, funcName);
    return std::make_unique<CallExprMatchRule>(elementName, funcType, funcName);
}

static std::string GetArgName(const Cangjie::AST::FuncDecl &fd, const FuncArg &arg)
{
    if (arg.TestAttr(Cangjie::AST::Attribute::IMPLICIT_ADD)) {
        // For trailing closure argument, its naming condition always follows the definition.
        if (!fd.funcBody->paramLists[0]->params.empty() && fd.funcBody->paramLists[0]->params.back()->isNamedParam) {
            return fd.funcBody->paramLists[0]->params.back()->identifier;
        } else {
            return "";
        }
    }
    return arg.name;
}

static Ptr<Cangjie::AST::Ty> GetInoutTargetTy(const OwnedPtr<FuncArg> &arg)
{
    if (Cangjie::AST::Ty::IsTyCorrect(arg->expr->ty)) {
        return arg->expr->ty;
    }
    CJC_ASSERT(arg->ty->IsPointer() && arg->ty->typeArgs.size() == 1);
    return arg->ty->typeArgs[0];
}

static void SortCallArgumentByParamOrder(
    const FuncDecl &fd, const CallExpr &ce, std::vector<Ptr<FuncArg>> &args)
{
    bool namedArgFound = false;
    size_t pos = 0;
    for (auto &arg : ce.args) {
        if (!arg->expr) {
            continue;
        }
        CJC_NULLPTR_CHECK(arg->expr);
        arg->expr->ty = arg->withInout ? GetInoutTargetTy(arg) : arg->ty;
        auto argName = GetArgName(fd, *arg);
        if (argName.empty()) {
            if (namedArgFound) {
                // Positional argument can not appear after named argument.
                break;
            }
            if (pos < args.size()) {
                args[pos] = arg.get();
            } else {
                // For varArg func.
                args.push_back(arg.get());
            }
            ++pos;
            continue;
        }
        namedArgFound = true;
        // Find the parameters whose name is the same as arguments. Then get the correct position where the
        // argument's type should be.
        for (size_t j = 0; j < fd.funcBody->paramLists[0]->params.size(); ++j) {
            if (fd.funcBody->paramLists[0]->params[j]->identifier == argName) {
                args[j] = arg.get();
                break;
            }
        }
    }
}

static std::optional<std::vector<Argument>> ExtractArguments(const CallExpr &callExpr)
{
    if (!callExpr.desugarArgs) {
        return std::vector<Argument>();
    }

    auto argsPtr = std::vector<Ptr<FuncArg>>();
    for (const auto &arg : callExpr.desugarArgs.value()) {
        if (!arg) {
            return std::nullopt;
        }
        argsPtr.push_back(arg.get());
    }

    // 传递临时向量
    if (!callExpr.resolvedFunction) {
        return std::nullopt;
    }
    SortCallArgumentByParamOrder(*callExpr.resolvedFunction, callExpr, argsPtr);

    auto arguments = std::vector<Argument>();
    for (auto &arg : argsPtr) {
        if (!arg->expr) {
            return std::nullopt;
        }
        const auto &expr = *arg->expr;

        // 检查是否是默认参数
        bool isDefaultArg = false;
        for (const auto &defaultArg : callExpr.defaultArgs) {
            if (defaultArg && defaultArg.get() == arg) {
                isDefaultArg = true;
                break;
            }
        }

        if (!isDefaultArg) { // 如果是用户提供的参数，存储其PositionRange
            arguments.emplace_back(PositionRange(expr));
        } else { // 如果是默认参数，以字符串形式存储
            arguments.emplace_back(expr.ToString());
        }
    }
    return arguments;
}

std::unique_ptr<ExtractedInfo> CallExprMatchRule::ExtractInfo(const Node &node, const PositionRange &range)
{
    if (node.astKind != ASTKind::CALL_EXPR) {
        return nullptr;
    }

    const auto &callExpr = Cast<ASTKind::CALL_EXPR>(node);
    const auto baseFunc = callExpr.baseFunc.get();
    if (!baseFunc) {
        return nullptr;
    }

    auto infos = GetBaseFuncInfo(*baseFunc);
    if (!infos.has_value()) {
        return nullptr;
    }
    auto [packageName, identifier, funcName, callerRange] = infos.value();

    auto packageAndIdentifier = packageName;
    if (identifier != "") { // else: extend
        packageAndIdentifier += "." + identifier;
    }

    const auto target = baseFunc->GetTarget();
    if (!target) {
        return nullptr;
    }
    const auto ty = target->ty;
    if (!ty) {
        return nullptr;
    }
    const auto funcType = Ty::ToString(ty);

    const auto arguments = ExtractArguments(callExpr);
    if (!arguments) {
        return nullptr;
    }

    return std::make_unique<CallExprExtractedInfo>(range, node,
        funcName, packageAndIdentifier, funcType, callerRange, std::move(arguments.value()));
}

using PlaceholderResolver = std::function<std::optional<std::string>(const std::string &)>;

static std::string ApplyPattern(const std::string &pattern, const PlaceholderResolver &resolver)
{
    std::string result;
    size_t i = 0;

    while (i < pattern.size()) {
        if (pattern[i] != '$') { // Ordinary character, append directly
            result += pattern[i];
            ++i;
            continue;
        }

        size_t start = i + 1;
        size_t end = start;

        while (end < pattern.size() && std::isalnum(pattern[end])) {
            ++end;
        }

        const auto key = pattern.substr(start, end - start);
        if (key.empty()) { // Lone '$' or invalid placeholder, treat as literal
            result += pattern[i];
            ++i;
            continue;
        }

        if (auto valueOpt = resolver(key)) {
            result += *valueOpt;
        } else { // Unknown placeholder
            result += '$' + key;
        }
        i = end;
    }

    return result;
}

static std::optional<Actions::Action> CallExprGenerateAction(
    UpdateInstance::File &file, const CallExprExtractedInfo &info, const std::string &update, const Rule *rule)
{
    const std::string callerName = file.GetContentInRange(info.callerPositionRange);
    if (callerName == "?") { // @NotSupport OptionalChain
        return Actions::Comment(info.replacePosition, Actions::Comment::Message::OptionalChain, rule);
    }

    for (const auto &argument : info.arguments) { // @NotSupport VariableArgument
        if (argument.IsRange() && argument.GetRange() == info.replacePosition) {
            return Actions::Comment(info.replacePosition, Actions::Comment::Message::VariableArgument, rule);
        }
    }

    bool errorOccurred = false;
    auto newExpr = ApplyPattern(update, [&](const std::string &key) -> std::optional<std::string> {
        if (key == "e") { // $e
            return callerName;
        }

        if (std::all_of(key.begin(), key.end(), ::isdigit)) { // $n
            size_t index = std::stoul(key);
            if (index >= info.arguments.size()) {
                errorOccurred = true; // Arguments out of range
                return std::nullopt;
            }
            const auto &arg = info.arguments[index];
            if (arg.IsRange()) {
                return file.GetContentInRange(arg.GetRange());
            } else {
                return arg.GetString();
            }
        }

        return std::nullopt;
    });
    if (errorOccurred) {
        return std::nullopt;
    }

    return Actions::Replace(info.replacePosition, newExpr, rule);
}

std::vector<Actions::Action> CallExprMatchRule::GenerateAction(
    UpdateInstance::File &file, const ExtractedInfo &info, const std::string &update, const Rule *rule) const
{
    if (update.empty()) {
        return {};
    }
    auto action = CallExprGenerateAction(file, dynamic_cast<const CallExprExtractedInfo &>(info), update, rule);
    if (!action.has_value()) {
        return {};
    }
    return { action.value() };
}

} // namespace CangjieMigrator::Rules
